﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.IO;
using System.Text.RegularExpressions;
using Vortex.Debugging;

namespace Vortex.SceneFramework.CSS {

	/// <summary>
	/// Implements CSS format parsing functionality
	/// </summary>
	public class CssParser {
		private readonly static char[] STATEMENT_SEPARATOR = new char[] { ';' };
		private readonly static char[] SELECTOR_SEPARATOR = new char[] { ',' };
		private readonly static char[] SELECTOR_STATE_SEPARATOR = new char[] { ':' };
		private readonly static char STYLE_PROPERTY_VALUE_SEPARATOR = ':';

		private readonly static Dictionary<string, StyleState> _stateMap = new Dictionary<string, StyleState>();

		private StyleSheet _styleSheet;

		/// <summary>
		/// Initializes the static components of class.
		/// </summary>
		static CssParser() {
			foreach (StyleState state in Enum.GetValues(typeof(StyleState))) {
				_stateMap.Add(state.ToString().ToLower(), state);
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CssParser"/> class targetting specified stylesheet.
		/// </summary>
		/// <param name="target">The parser target style sheet.</param>
		public CssParser(StyleSheet target) {
			_styleSheet = target;
		}

		/// <summary>
		/// Parses the CSS text.
		/// </summary>
		/// <param name="cssText">The CSS text.</param>
		public void Parse(string cssText) {
			//Step 1. Clean CSS
			cssText = CleanCss(cssText);
			//Step 2. Find all of correct constructions
			MatchCollection collection = Regex.Matches(cssText, "(.*?){(.*?)}");
			//Step 3. Process all of matches as style classses
			foreach (Match match in collection) {
				string selectorString = match.Groups[1].Value;
				string bodyString = match.Groups[2].Value;

				//convert selector string into Enumeration of style value collection.
				IEnumerable<StyleValueSet> targets = ParseSelectors(selectorString);
				uint blockImportance = StyleValue.PromoteImportance();

				string[] statementStringArray = bodyString.Split(STATEMENT_SEPARATOR, StringSplitOptions.RemoveEmptyEntries);
				foreach (string statement in statementStringArray) {
					int colonIndex = statement.IndexOf(STYLE_PROPERTY_VALUE_SEPARATOR);
					if (colonIndex > 0 && colonIndex < (statement.Length-1)) {
						string propertyName = statement.Substring(0, colonIndex).Trim();
						string propertyValue = statement.Substring(colonIndex + 1).Trim();

						StyleKey property = new StyleKey(propertyName);
						StyleValue styleValue = new StyleValue(_styleSheet.ResolveValue(property, propertyValue), blockImportance);
						//set it to each value collection
						foreach (StyleValueSet target in targets) {
							target[property] = styleValue;
						}
						//process next property
						continue;
					}
					Log.Warning("Unable to recognize CSS statement '{0}'", statement);
				}
			}
		}

		/// <summary>
		/// Parses the CSS selector string.
		/// </summary>
		/// <param name="selectorString">The selector string.</param>
		/// <returns>Enumeration of target <see cref="StyleValueCollection"/></returns>
		private IEnumerable<StyleValueSet> ParseSelectors(string selectorString) {
			string[] selectorStringArray = selectorString.Split(SELECTOR_SEPARATOR, StringSplitOptions.RemoveEmptyEntries);
			foreach (string selector in selectorStringArray) {
				string[] selectorParts = selector.Trim().Split(SELECTOR_STATE_SEPARATOR);
				if (selectorParts.Length <= 2) {
					StyleState state = StyleState.Undefined;
					if (selectorParts.Length == 2) {
						if (!ResolveStateSelector(selectorParts[1], out state)) {
							Log.Warning("Unknown selector state '{0}'", selectorParts[1]);
							continue;
						}
					}

					if (selectorParts[0].Length > 0) {
						//try to get style class
						StyleClass styleClass = _styleSheet.GetOrCreateEntity(selectorParts[0]);
						yield return styleClass.GetOrCreateStateValueSet(state);
					} else {
						Log.Warning("Malformed style selector '{0}'", selector);
					}
				} else {
					Log.Warning("Unable to recognize selector '{0}'", selector);
				}
			}
		}

		private bool ResolveStateSelector(string stateString, out StyleState state) {
			return _stateMap.TryGetValue(stateString.ToLower(), out state);
		}

		private string CleanCss(string cssText) {
			//remove all of non-space separators
			cssText = cssText.Replace("\n", "").Replace("\t", "").Replace("\r", "");
			//remove all of comments from text
			return Regex.Replace(cssText, "[/][*](.*?)[*][/]", "");
		}

	}
}
